package sk.naive.talker.command;

import sk.naive.talker.*;
import sk.naive.talker.util.UserContextHelper;
import sk.naive.talker.message.*;
import sk.naive.talker.util.*;
import sk.naive.talker.props.*;

import java.util.*;
import java.util.logging.Logger;
import java.rmi.RemoteException;

/**
 * Skeletal command implementation.
 *
 * @author <a href="mailto:virgo@naive.deepblue.sk">Richard "Virgo" Richter</a>
 * @version $Revision: 1.45 $ $Date: 2005/01/19 22:41:26 $
 */
public abstract class AbstractCommand
	implements Command
{
	protected CommandDispatcher commandDispatcher;
	protected String command;
	protected User user;
	protected String params;
	private MessageSendHelper messageSendHelper = new MessageSendHelper(this);

	public final void exec(CommandParameters parameters) throws CommandException, RemoteException {
		commandDispatcher = parameters.getCommandDispatcher();
		command = parameters.getCommand();
		user = parameters.getUser();
		params = parameters.getParams();

		try {
			exec();
		} catch (PropertyStoreException e) {
			throw new CommandException(e);
		}
	}

	protected void exec() throws CommandException, RemoteException, PropertyStoreException {
	}

	protected MessageFactory messageFactory() {
		return commandDispatcher.getMessageFactory();
	}

	protected Logger logger() {
		return commandDispatcher.getLogger();
	}

	protected String getString(String key, Map ctx) {
		return messageFactory().getString(key, ctx);
	}

	protected String getString(String key, Map ctx, String lang) {
		return messageFactory().getString(key, ctx, lang);
	}

	protected Message getMessage(String key, Map ctx) {
		return messageFactory().createMessage(key, ctx);
	}

	protected String misusageWarning() {
		return "command.defaultMisusage";
	}

	protected String userNotFoundWarning() {
		return "command.userDoesNotExist";
	}

	protected String notLoggedInWarning() {
		return "command.userNotLoggedIn";
	}

	protected String selfWarning() {
		return "command.selfWarning";
	}

	/**
	 * Sending message for other user than the command invoker.
	 * <p>
	 * The key difference is that ALWAYS sender's properties are used as the
	 * message context. Using sendMessage(recipient, key) is wrong if recicpient
	 * is not a command invoker.
	 *
	 * @param rec recipient of the message
	 * @param key message key
	 * @throws RemoteException
	 */
	protected void sendRecipientMessage(User rec, String key) throws RemoteException {
		rec.send(getString(key, user.getProperties(), rec.getLang()));
	}

	protected void actionNotify(User notified, String messageKey) throws RemoteException {
		if (notified.isOnline()) {
			sendRecipientMessage(notified, messageKey);
		} else {
			/* TODO: mail notify, niekde inde, nejak inak, cez plugin!
			try {
				MailHelper mh = (MailHelper) getTalker().getProperties().get(MailHelper.TPROP_MAIL_HELPER);
				mh.sendMail(
					invoker.getLogin(),
					mh.createAddresses(notified.getLogin()),
					getString(messageKey, invoker.getProperties(), notified.getLang()),
					""
				);
			} catch (MessagingException e) {
				throw new CommandException(e);
			}
			*/
		}
	}

	protected Talker getTalker() {
		return commandDispatcher.getTalker();
	}

	/**
	 * Used for adding things into the context of specified user.
	 * <p />
	 * You should use ctxSet(key, value) for setting values of the command performer's context.
	 *
	 * @param u specified user - do not use for user executing this command
	 * @param ctxKey context key
	 * @param value value object
	 */
	protected void ctxSet(User u, String ctxKey, Object value) {
		new UserContextHelper(u).set(ctxKey, value);
	}

	/**
	 * Used for adding things into the context of the user performing command.
	 *
	 * @param ctxKey context key
	 * @param value value object
	 */
	protected void ctxSet(String ctxKey, Object value) {
		// TODO potom staci na cely beh jedna instancia UCH ;-)
		new UserContextHelper(user).set(ctxKey, value);
	}

	/**
	 * Sending message to all users in the specified collection except one.
	 * @param m Message to send
	 * @param audience target group of users
	 * @param excluded excepted users
	 * @throws RemoteException
	 */
	protected void sendMassMessage(Message m, Collection<User> audience, final Collection excluded, final Category category)
		throws RemoteException
	{
		// TODO: Dorobit moznost nejakej vynimky pre ignore (ked je autor wiz, atd) - bude riesene tretim TAG systemom...
		// interpretovanie tagov pri vypise (tj. skor ako to odide na adapter)
		getTalker().sendToUsers(m, audience, new UserExcluder() {
			public boolean isExcluded(User u) {
				return (Utils.isIgnoring(u, category) || (excluded != null && excluded.contains(u)));
			}
		});
	}

	protected Buffer getBuffer(PropertyStore propStore, String propName)
		throws CommandException
	{
		return getBuffer(propStore, propName, Consts.DEFAULT_BUFFER_SIZE);
	}

	protected Buffer getBuffer(PropertyStore propStore, String propName, int size) {
		Buffer review = (Buffer) propStore.getProperties().get(propName);
		if (review == null) {
			review = new Buffer(size);
			propStore.getProperties().put(propName, review);
		}
		return review;
	}

	protected String targetUserNotFoundWarning() {
		return misusageWarning();
	}

	protected boolean isSecureTargetSearch() {
		return true;
	}

	protected void performTargetNotFoundWarning(User user, String recipientLogin) throws RemoteException, PropertyStoreException {
		user.getProperties().put(DefaultMessageFactory.CTXKEY_VAL, recipientLogin);
		sendHelper().sendMessage(user, targetNotFoundWarning());
	}

	protected String targetNotFoundWarning() {
		return notLoggedInWarning();
	}

	protected String secureWarning() {
		return "message.secureWarning";
	}

	protected Collection<User> getPossibleTargetUsers(User u) {
		return commandDispatcher.getUserFinder().onlineUsers();
	}

	protected User findTarget(String targetLogin) throws CommandException {
		try {
			if (targetLogin == null) {
				sendHelper().sendMessage(user, targetUserNotFoundWarning());
				return null;
			}
			UserFindHelper ufh = new UserFindHelper(getPossibleTargetUsers(user));
			ufh.takeOut(user);
			if (!ufh.find(targetLogin)) {
				if (user.getLogin().startsWith(targetLogin)) {
					if (selfTargetAllowed()) {
						return user;
					}
					sendHelper().sendMessage(user, selfWarning());
				} else {
					performTargetNotFoundWarning(user, targetLogin);
				}
				return null;
			}
			User target = ufh.firstFound();
			if (isSecureTargetSearch()) {
				if (!ufh.isSecure() && !target.getLogin().equals(targetLogin)) {
					ctxSet(DefaultMessageFactory.CTXKEY_VAL, targetLogin);
					sendHelper().sendMessage(user, secureWarning());
					return null;
				}
			}
			return target;
		} catch (RemoteException e) {
			throw new CommandException(e);
		} catch (PropertyStoreException e) {
			throw new CommandException(e);
		}
	}

	protected boolean selfTargetAllowed() {
		return false;
	}

	protected void bufferCommandUsage(String command, StringBuilder sb) {
		String usage = commandDispatcher.getHelpMessages().getString("c." + command + ".usage", user.getProperties());
		if (usage != null && usage.length() > 0) {
			sb.append(getString("command.usage.head", user.getProperties()));
			for (String s : usage.split("<br>")) {
				ctxSet(DefaultMessageFactory.CTXKEY_VAL, s);
				sb.append(getString("command.usage.line", user.getProperties()));
			}
			sb.append(getString("command.usage.tail", user.getProperties()));
		}
	}

	protected void sendMisusageWarning() throws RemoteException {
		StringBuilder sb = new StringBuilder();
		sb.append(getString(misusageWarning(), user.getProperties()));
		bufferCommandUsage(command, sb);
		user.send(sb.toString());
	}

	protected void sendUserNotFoundMessage(String value) throws RemoteException {
		user.getProperties().put(DefaultMessageFactory.CTXKEY_VAL, value);
		sendHelper().sendMessage(user, userNotFoundWarning());
	}

	protected MessageSendHelper sendHelper() {
		return messageSendHelper;
	}
}